package mac10.bean;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;

import mac10.connection.MessageConstants;
import mac10.messages.OrderMessage;
import mac10.messages.PerceptionsFinishedMessage;
import mac10.messages.RegisterMessage;
import mac10.messages.SimEndMessage;
import mac10.messages.SimStartMessage;
import mac10.models.Cell;
import mac10.models.ClusterBook;
import mac10.models.WorldModel;
import mac10.monitor.NewMonitor;
import mac10.roles.MasterExplorer;
import mac10.roles.MasterShepherd;
import mac10.roles.MasterSwitcher;

import org.sercho.masp.space.event.SpaceEvent;
import org.sercho.masp.space.event.SpaceObserver;
import org.sercho.masp.space.event.WriteCallEvent;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import de.dailab.jiactng.agentcore.IAgentBean;
import de.dailab.jiactng.agentcore.action.AbstractMethodExposingBean;
import de.dailab.jiactng.agentcore.action.Action;
import de.dailab.jiactng.agentcore.comm.message.IJiacMessage;
import de.dailab.jiactng.agentcore.comm.message.JiacMessage;
import de.dailab.jiactng.agentcore.knowledge.IFact;
import de.dailab.jiactng.agentcore.ontology.IAgentDescription;

/**
 * The <code>MasterBean</code> coordinates the team.
 * 
 * @author axle
 * @see de.dailab.jiactng.agentcore.AbstractMethodExposingBean
 */
public class MasterBean extends AbstractMethodExposingBean implements
		MessageConstants {

	public static Map<IAgentDescription, Cell> agents = new HashMap<IAgentDescription, Cell>();
	public static PriorityQueue<IAgentDescription> sortedAgents = new PriorityQueue<IAgentDescription>(
			20, new Comparator<IAgentDescription>() {
				public int compare(IAgentDescription o1, IAgentDescription o2) {
				  if(o1 == null) {
				    System.err.println(" \n\t ### ### compare, o1 = null, "+o1);
				  }
          if(o1.getName() == null) {
            System.err.println(" \n\t ### ### compare, o1.getName = null, "+o1);
          }
				  
				  if(o2 == null) {
            System.err.println(" \n\t ### ### compare, o2 = null, "+o2);
          }
          if(o2.getName() == null) {
            System.err.println(" \n\t ### ### compare, o2.getName = null, "+o2);
          }
				  
					return o1.getName().compareTo(o2.getName());
				}
			});
	private ArrayList<IAgentDescription> permExplorer;
	private Map<IAgentDescription, Cell> switcher = new HashMap<IAgentDescription, Cell>();
	private Map<IAgentDescription, String> shepherds = new HashMap<IAgentDescription, String>();
	private WorldModel worldModel;
	private String simulationID = null;
	private int simulationStep = -1;
	static private long shortestDeadline = Long.MAX_VALUE;
	private boolean simRunning = false;
	private static boolean monitorUpdatedThisStep = false;
	private int perceptionsReceivedInStep = 0;
	public static NewMonitor newMonitor;

	
	/** Actions to communicate with other team members. */
	private Action sendMessageAction;

	static public long getShortestDeadline() {
		return shortestDeadline;
	}
	
	
	@Override
	public void doInit() throws Exception {
		super.doInit();

		for (IAgentBean ab : thisAgent.getAgentBeans()) {
			if (ab instanceof ServerCommunicationBean) {
				// this.serverCommunicationBean = (ServerCommunicationBean)ab;
			}
		}
	}

	@Override
	public void doStart() throws Exception {
		super.doStart();
		log.info("MasterAgent starting....");
		log.info("Ping - my ID: " + this.thisAgent.getAgentId());
		log.info("Ping - my Name: " + this.thisAgent.getAgentName());
		log.info("Ping - my Node: " + this.thisAgent.getAgentNode().getName());

		// Retrieve my own send action
		sendMessageAction = retrieveAction("de.dailab.jiactng.agentcore.comm.ICommunicationBean#send");

		// If no send action is available, check your agent configuration.
		// CommunicationBean is needed
		if (sendMessageAction == null)
			throw new RuntimeException("cannot send - no local action exists");

		// listen to memory events, see MessageObserver implementation below
		memory.attach(new MessageObserver(), new JiacMessage());
	}

	/**
	 * Creates the authentication response.
	 * 
	 * @param message
	 *            the message
	 * 
	 * @return the authentication response
	 */
	private void processRegistration(RegisterMessage message) {
		agents.put(message.getSenderDescription(), null);
		sortedAgents.add(message.getSenderDescription());
		log.warn("registration received: "
				+ message.getSenderDescription().getName());
	}

	/**
	 * Creates the simulation start.
	 * 
	 * @param message
	 *            the message
	 * 
	 * @return the simulation start
	 */
	private void processSimulationStart(SimStartMessage message) {
		NodeList nodes = message.getSimStart().getElementsByTagName(
				"simulation");
		Element childPerception = (Element) nodes.item(0);

		String id = childPerception.getAttribute("id");
		// if already in simulation do nothing
		if (id.equals(this.simulationID) || this.simRunning)
			return;
		this.simulationID = id;
		this.simulationStep = 0;
		String opponent = childPerception.getAttribute("opponent");
		int steps = Integer.parseInt(childPerception.getAttribute("steps"));
		int gsizex = Integer.parseInt(childPerception.getAttribute("gsizex"));
		int gsizey = Integer.parseInt(childPerception.getAttribute("gsizey"));
		int corralx0 = Integer.parseInt(childPerception
				.getAttribute("corralx0"));
		int corraly0 = Integer.parseInt(childPerception
				.getAttribute("corraly0"));
		int corralx1 = Integer.parseInt(childPerception
				.getAttribute("corralx1"));
		int corraly1 = Integer.parseInt(childPerception
				.getAttribute("corraly1"));
		int lineOfSight = Integer.parseInt(childPerception
				.getAttribute("lineOfSight"));
		log.warn("Simulation start message received with data:" + " id=" + id
				+ " opponent=" + opponent + " steps=" + steps + " gridSizeX="
				+ gsizex + " gridSizeY=" + gsizey + " corralX0=" + corralx0
				+ " corralY0=" + corraly0 + " corralX1=" + corralx1
				+ " corralY1=" + corraly1 + " lineOfSight=" + lineOfSight);

		this.initializeWorldModel(gsizex, gsizey, steps, lineOfSight, corralx0, corraly0,
				corralx1, corraly1);
		
		MasterSwitcher.staticMasterSwitcher = new MasterSwitcher(worldModel);
		MasterShepherd.staticMasterShepherd = new MasterShepherd(worldModel);
		ClusterBook.staticClusterBook = new ClusterBook();
		newMonitor = new NewMonitor(worldModel);

		this.simRunning = true;
	}

	/**
	 * Creates the simulation end.
	 * 
	 * @param message
	 *            the message
	 * 
	 * @return the simulation end
	 */
	private void processSimulationEnd(SimEndMessage message) {
		NodeList nodes = message.getSimEnd().getElementsByTagName("sim-result");
		Element childPerception = (Element) nodes.item(0);

		if (this.simulationID == null || !this.simRunning)
			return;
		
		WorldModel.eraseOldWorldModels(Integer.parseInt(simulationID));
		
		this.simulationID = null;
		this.simulationStep = -1;
		this.simRunning = false;
		String result = childPerception.getAttribute("result");
		double score = Double.parseDouble(childPerception.getAttribute("averageScore"));
		log.warn("Simulation end message received: result=" + result
				+ " score=" + score);
		this.worldModel = null;
		WorldModel.staticWorldModel = null;
		MasterSwitcher.staticMasterSwitcher = null;
		MasterShepherd.staticMasterShepherd = null;
		ClusterBook.staticClusterBook = null;
		MasterExplorer.setLastStep(-1);
		MasterBean.shortestDeadline = Long.MAX_VALUE;
		switcher.clear();
	}

	private void processPerceptionsFinished(PerceptionsFinishedMessage message) {
		
		int step = message.getStep();
		if (step < this.simulationStep)
			return;
		if (step > this.simulationStep) {
			this.perceptionsReceivedInStep = 0;
			this.simulationStep = step;
			MasterBean.monitorUpdatedThisStep = false;
		}
		this.perceptionsReceivedInStep++;
		MasterBean.shortestDeadline = System.currentTimeMillis()+2000;
        WorldModel.staticWorldModel.removeMarkedFlags();
        WorldModel.staticWorldModel.removeCowsWithUnknownLocation();
        
        //try different method of herd detection
        WorldModel.staticWorldModel.clearHerds();
        WorldModel.staticWorldModel.computeEdgeDetection();
        
		computeStrategy();
	}

	private synchronized void initializeWorldModel(int gsizex, int gsizey,
			int steps, int lineOfSight, int corralx0, int corraly0, int corralx1, int corraly1) {
		worldModel = WorldModel.loadWorldModelFromFile(Integer.parseInt(simulationID));
		if(worldModel == null){
			worldModel = new WorldModel(gsizex, gsizey, steps, lineOfSight, corralx0, corraly0,
					corralx1, corraly1);
		}
		worldModel.setTeamName(this.thisAgent.getAgentName());
		WorldModel.staticWorldModel = worldModel;
	}
	
	
	
	private void assureExplorer()
	{
	    if(permExplorer == null)
	    {
	        permExplorer = new ArrayList<IAgentDescription>();
	        for (IAgentDescription agent : agents.keySet())
            {
	            if(permExplorer.size() < 2)
	            {
	                permExplorer.add(agent);
	            }
            }
	    }
	}
	private void computeStrategy() {
		// log.info("updating world model...");
		if (!MasterBean.monitorUpdatedThisStep) {
			newMonitor.updateMonitor(worldModel);
			MasterBean.monitorUpdatedThisStep = true;
		}

		WorldModel.staticWorldModel.agentTargets.clear();
		
		log.info(this.thisAgent.getAgentName()
				+ ": computing strategy for step " + this.simulationStep);
		
		if (MasterExplorer.getLastStep() == -1) {
			MasterExplorer.initiateMasterExplorer(this.worldModel);
			System.out.println("master exlorer");
		}
			
		MasterExplorer.filterKnownPoints(this.worldModel, true);
		System.out.println("sad"+MasterExplorer.getExpPoints().size());
		if(MasterExplorer.getExpPoints().isEmpty() || MasterExplorer.getExpPoints().size() < 2)
		{
			System.out.println("exp points empty");
			
			MasterExplorer.setExpPoints(MasterExplorer.createExpPoints(this.worldModel));
			MasterExplorer.filterKnownPoints(this.worldModel, false);
//			System.out.println(MasterExplorer.getExpPoints().size());
//			for (Cell foo : MasterExplorer.getExpPoints())
//			{
//				foo.setMarked(Cell.BLUE_CIRCLE);
//			}
						
		}

		
		assureExplorer();
		
	    Map<IAgentDescription, Cell> agentsWithoutPermExpl = new HashMap<IAgentDescription, Cell>(agents);
        for (IAgentDescription agent : permExplorer)
        {
            agentsWithoutPermExpl.remove(agent);
        }

		switcher = MasterSwitcher.staticMasterSwitcher.currentSwitchers(agents);
		
		
		
		Map<IAgentDescription, Cell> possibleSheperds = new HashMap<IAgentDescription, Cell>(agentsWithoutPermExpl);
		for (IAgentDescription agent : switcher.keySet())
        {
            possibleSheperds.remove(agent);
        }
		

		long start = System.currentTimeMillis();
		shepherds = MasterShepherd.staticMasterShepherd.currentShepherds(possibleSheperds, simulationStep, 2);
		long end = System.currentTimeMillis();
		System.err.println(" shepherds "+ (end- start));
		
		int nrOfExplorers = 0, nrOfSwitchers = 0, nrOfShepherds = 0;
		// TODO: compute strategy
		for (IAgentDescription agent : sortedAgents) 
		{
			String task = "";
			if (switcher.keySet().contains(agent)) 
			{
				nrOfSwitchers++;
				task = ((Cell)switcher.get(agent)).postionToSting();
                sendOrderMessage(new OrderMessage("switcher", task,
                        this.simulationStep), agent);
			} 
			else if(shepherds.keySet().contains(agent))
			{
				nrOfShepherds++;
			    task = shepherds.get(agent);
                sendOrderMessage(new OrderMessage("shepherd", task,
                        this.simulationStep), agent);
			}
			else 
			{
				nrOfExplorers++;
				task = "explore";
				sendOrderMessage(new OrderMessage("explorer", task,
                        this.simulationStep), agent);
			}


		}
		
		MasterBean.shortestDeadline = Long.MAX_VALUE;
		System.out.println("\tExplorers: " + nrOfExplorers+
				", Switchers: " + nrOfSwitchers + ", Shepherds: "+nrOfShepherds+ ", NrOfKnownCows: "+WorldModel.staticWorldModel.getCowsAsCellList().size());
		
		
        MasterBean.newMonitor.repaint();  

		//System.out.println("WM_currentStep: "+ worldModel.getCurrStep());
		//WorldModel.saveWorldModelToFile(worldModel, Integer.parseInt(simulationID), simulationStep);
		//WorldModel wm_temp =  WorldModel.loadWorldModelFromFile(Integer.parseInt(simulationID));
		//if(wm_temp != null) this.worldModel = wm_temp;
	}

	private void sendOrderMessage(OrderMessage order, IAgentDescription receiver) {
		// log.info("sending order to " + receiver.getMessageBoxAddress());
		invoke(sendMessageAction, new Serializable[] { new JiacMessage(order),
				receiver.getMessageBoxAddress() });
	}

	// this observer checks for memory events (see tuplespace docu for more
	// info)
	private class MessageObserver implements SpaceObserver<IFact> {

		private static final long serialVersionUID = 1L;

		@SuppressWarnings("unchecked")
		public void notify(SpaceEvent<? extends IFact> event) {
			// check only for write events in memory
			if (event instanceof WriteCallEvent<?>) {
				WriteCallEvent<IJiacMessage> wce = (WriteCallEvent<IJiacMessage>) event;

				// consume message
				IJiacMessage message = memory.remove(wce.getObject());
				// log.info(message);
//				if (message.getPayload() instanceof PerceptionMessage)
//					processPerception((PerceptionMessage) message.getPayload());
				if (message.getPayload() instanceof RegisterMessage)
					processRegistration((RegisterMessage) message.getPayload());
				if (message.getPayload() instanceof SimStartMessage)
					processSimulationStart((SimStartMessage) message
							.getPayload());
				if (message.getPayload() instanceof SimEndMessage)
					processSimulationEnd((SimEndMessage) message.getPayload());
				if (message.getPayload() instanceof PerceptionsFinishedMessage)
					processPerceptionsFinished((PerceptionsFinishedMessage) message
							.getPayload());
			}
		}

	}
}